diff --git a/kpimidentities/signature.cpp b/kpimidentities/signature.cpp index 5bd23bbe5..200a2d496 100644 --- a/kpimidentities/signature.cpp +++ b/kpimidentities/signature.cpp @@ -1,549 +1,586 @@ /* Copyright (c) 2002-2004 Marc Mutz Copyright (c) 2007 Tom Albers Copyright (c) 2009 Thomas McGuire This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "signature.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPIMIdentities; class SignaturePrivate { public: struct EmbeddedImage { QImage image; QString name; }; typedef QSharedPointer EmbeddedImagePtr; /// List of images that belong to this signature. Either added by addImage() or /// by readConfig(). QList embeddedImages; /// The directory where the images will be saved to. QString saveLocation; }; QDataStream &operator<< ( QDataStream &stream, const SignaturePrivate::EmbeddedImagePtr &img ) { return stream << img->image << img->name; } QDataStream &operator>> ( QDataStream &stream, SignaturePrivate::EmbeddedImagePtr &img ) { return stream >> img->image >> img->name; } // TODO: KDE5: BIC: Add a real d-pointer. // This QHash is just a workaround around BIC issues, for more info see // http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++ typedef QHash SigPrivateHash; Q_GLOBAL_STATIC(SigPrivateHash, d_func) static SignaturePrivate* d( const Signature *sig ) { SignaturePrivate *ret = d_func()->value( sig, 0 ); if ( !ret ) { ret = new SignaturePrivate; d_func()->insert( sig, ret ); } return ret; } static void delete_d( const Signature* sig ) { SignaturePrivate *ret = d_func()->value( sig, 0 ); delete ret; d_func()->remove( sig ); } Signature::Signature() : mType( Disabled ), mInlinedHtml( false ) {} Signature::Signature( const QString &text ) : mText( text ), mType( Inlined ), mInlinedHtml( false ) {} Signature::Signature( const QString &url, bool isExecutable ) : mUrl( url ), mType( isExecutable ? FromCommand : FromFile ), mInlinedHtml( false ) {} void Signature::assignFrom ( const KPIMIdentities::Signature &that ) { mUrl = that.mUrl; mInlinedHtml = that.mInlinedHtml; mText = that.mText; mType = that.mType; d( this )->saveLocation = d( &that )->saveLocation; d( this )->embeddedImages = d( &that )->embeddedImages; } Signature::Signature ( const Signature &that ) { assignFrom( that ); } Signature& Signature::operator= ( const KPIMIdentities::Signature & that ) { if ( this == &that ) return *this; assignFrom( that ); return *this; } Signature::~Signature() { delete_d( this ); } QString Signature::rawText( bool *ok ) const { switch ( mType ) { case Disabled: if ( ok ) { *ok = true; } return QString(); case Inlined: if ( ok ) { *ok = true; } return mText; case FromFile: return textFromFile( ok ); case FromCommand: return textFromCommand( ok ); }; kFatal(5325) << "Signature::type() returned unknown value!"; return QString(); // make compiler happy } QString Signature::textFromCommand( bool *ok ) const { assert( mType == FromCommand ); // handle pathological cases: if ( mUrl.isEmpty() ) { if ( ok ) { *ok = true; } return QString(); } // create a shell process: KProcess proc; proc.setOutputChannelMode( KProcess::SeparateChannels ); proc.setShellCommand( mUrl ); int rc = proc.execute(); // handle errors, if any: if ( rc != 0 ) { if ( ok ) { *ok = false; } QString wmsg = i18n( "Failed to execute signature script

%1:

" "

%2

", mUrl, QString( proc.readAllStandardError() ) ); KMessageBox::error( 0, wmsg ); return QString(); } // no errors: if ( ok ) { *ok = true; } // get output: QByteArray output = proc.readAllStandardOutput(); // TODO: hmm, should we allow other encodings, too? return QString::fromLocal8Bit( output.data(), output.size() ); } QString Signature::textFromFile( bool *ok ) const { assert( mType == FromFile ); // TODO: Use KIO::NetAccess to download non-local files! if ( !KUrl( mUrl ).isLocalFile() && !( QFileInfo( mUrl ).isRelative() && QFileInfo( mUrl ).exists() ) ) { kDebug(5325) << "Signature::textFromFile:" << "non-local URLs are unsupported"; if ( ok ) { *ok = false; } return QString(); } if ( ok ) { *ok = true; } // TODO: hmm, should we allow other encodings, too? const QByteArray ba = KPIMUtils::kFileToByteArray( mUrl, false ); return QString::fromLocal8Bit( ba.data(), ba.size() ); } QString Signature::withSeparator( bool *ok ) const { QString signature = rawText( ok ); if ( ok && (*ok) == false ) return QString(); if ( signature.isEmpty() ) { return signature; // don't add a separator in this case } QString newline = ( isInlinedHtml() && mType == Inlined ) ? "
" : "\n"; if ( signature.startsWith( QString::fromLatin1( "-- " ) + newline ) || ( signature.indexOf( newline + QString::fromLatin1( "-- " ) + newline ) != -1 ) ) { // already have signature separator at start of sig or inside sig: return signature; } else { // need to prepend one: return QString::fromLatin1( "-- " ) + newline + signature; } } void Signature::setUrl( const QString &url, bool isExecutable ) { mUrl = url; mType = isExecutable ? FromCommand : FromFile; } void Signature::setInlinedHtml( bool isHtml ) { mInlinedHtml = isHtml; } bool Signature::isInlinedHtml() const { return mInlinedHtml; } // config keys and values: static const char sigTypeKey[] = "Signature Type"; static const char sigTypeInlineValue[] = "inline"; static const char sigTypeFileValue[] = "file"; static const char sigTypeCommandValue[] = "command"; static const char sigTypeDisabledValue[] = "disabled"; static const char sigTextKey[] = "Inline Signature"; static const char sigFileKey[] = "Signature File"; static const char sigCommandKey[] = "Signature Command"; static const char sigTypeInlinedHtmlKey[] = "Inlined Html"; static const char sigImageLocation[] = "Image Location"; +// Returns the names of all images in the HTML code +static QStringList findImageNames( const QString &htmlCode ) +{ + QStringList ret; + + // To complicated for us, so cheat and let a text edit do the hard work + KPIMTextEdit::TextEdit edit; + edit.setHtml( htmlCode ); + foreach( const KPIMTextEdit::ImageWithNamePtr &image, edit.imagesWithName() ) { + ret << image->name; + } + return ret; +} + +void Signature::cleanupImages() const +{ + // Remove any images from the internal structure that are no longer there + if ( isInlinedHtml() ) { + foreach( const SignaturePrivate::EmbeddedImagePtr imageInList, d( this )->embeddedImages ) { + bool found = false; + foreach( const QString &imageInHtml, findImageNames( mText ) ) { + if ( imageInHtml == imageInList->name ) { + found = true; + break; + } + } + if ( !found ) + d( this )->embeddedImages.removeAll( imageInList ); + } + } + + // Delete all the old image files + if ( !d( this )->saveLocation.isEmpty() ) { + QDir dir( d( this )->saveLocation ); + foreach( const QString &fileName, dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ) ) { + if ( fileName.toLower().endsWith( ".png" ) ) { + kDebug() << "Deleting old image" << dir.path() + fileName; + dir.remove( fileName ); + } + } + } +} + +void Signature::saveImages() const +{ + if ( isInlinedHtml() && !d( this )->saveLocation.isEmpty() ) { + foreach( const SignaturePrivate::EmbeddedImagePtr &image, d( this )->embeddedImages ) { + QString location = d( this )->saveLocation + '/' + image->name; + if ( !image->image.save( location, "PNG" ) ) { + kWarning() << "Failed to save image" << location; + } + } + } +} + void Signature::readConfig( const KConfigGroup &config ) { QString sigType = config.readEntry( sigTypeKey ); if ( sigType == sigTypeInlineValue ) { mType = Inlined; mInlinedHtml = config.readEntry( sigTypeInlinedHtmlKey, false ); } else if ( sigType == sigTypeFileValue ) { mType = FromFile; mUrl = config.readPathEntry( sigFileKey, QString() ); } else if ( sigType == sigTypeCommandValue ) { mType = FromCommand; mUrl = config.readPathEntry( sigCommandKey, QString() ); } else { mType = Disabled; } mText = config.readEntry( sigTextKey ); d( this )->saveLocation = config.readEntry( sigImageLocation ); if ( isInlinedHtml() && !d( this )->saveLocation.isEmpty() ) { QDir dir( d( this )->saveLocation ); foreach( const QString &fileName, dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ) ) { if ( fileName.toLower().endsWith( ".png" ) ) { QImage image; if ( image.load( dir.path() + '/' + fileName ) ) { addImage( image, fileName ); } else { kWarning() << "Unable to load image" << dir.path() + '/' + fileName; } } } } } void Signature::writeConfig( KConfigGroup &config ) const { switch ( mType ) { case Inlined: config.writeEntry( sigTypeKey, sigTypeInlineValue ); config.writeEntry( sigTypeInlinedHtmlKey, mInlinedHtml ); break; case FromFile: config.writeEntry( sigTypeKey, sigTypeFileValue ); config.writePathEntry( sigFileKey, mUrl ); break; case FromCommand: config.writeEntry( sigTypeKey, sigTypeCommandValue ); config.writePathEntry( sigCommandKey, mUrl ); break; case Disabled: config.writeEntry( sigTypeKey, sigTypeDisabledValue ); default: break; } config.writeEntry( sigTextKey, mText ); config.writeEntry( sigImageLocation, d( this )->saveLocation ); - // First delete the old image files - if ( !d( this )->saveLocation.isEmpty() ) { - QDir dir( d( this )->saveLocation ); - foreach( const QString &fileName, dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ) ) { - if ( fileName.toLower().endsWith( ".png" ) ) { - kDebug() << "Deleting old image" << dir.path() + fileName; - dir.remove( fileName ); - } - } - } - - // Then, save the new images - if ( isInlinedHtml() && !d( this )->saveLocation.isEmpty() ) { - foreach( const SignaturePrivate::EmbeddedImagePtr &image, d( this )->embeddedImages ) { - QString location = d( this )->saveLocation + '/' + image->name; - if ( !image->image.save( location, "PNG" ) ) { - kWarning() << "Failed to save image" << location; - } - } - } + cleanupImages(); + saveImages(); } void Signature::insertIntoTextEdit( KRichTextEdit *textEdit, Placement placement, bool addSeparator ) { // Bah. const_cast( this )->insertIntoTextEdit( textEdit, placement, addSeparator ); } void Signature::insertIntoTextEdit( KRichTextEdit *textEdit, Placement placement, bool addSeparator ) const { QString signature; if ( addSeparator ) signature = withSeparator(); else signature = rawText(); insertPlainSignatureIntoTextEdit( signature, textEdit, placement, ( isInlinedHtml() && type() == KPIMIdentities::Signature::Inlined ) ); // We added the text of the signature above, now it is time to add the images as well. KPIMTextEdit::TextEdit *pimEdit = dynamic_cast( textEdit ); if ( pimEdit && isInlinedHtml() ) { foreach( const SignaturePrivate::EmbeddedImagePtr &image, d( this )->embeddedImages ) { pimEdit->loadImage( image->image, image->name, image->name ); } } } void Signature::insertPlainSignatureIntoTextEdit( const QString &signature, KRichTextEdit *textEdit, Signature::Placement placement, bool isHtml ) { if ( !signature.isEmpty() ) { // Save the modified state of the document, as inserting a signature // shouldn't change this. Restore it at the end of this function. bool isModified = textEdit->document()->isModified(); // Move to the desired position, where the signature should be inserted QTextCursor cursor = textEdit->textCursor(); QTextCursor oldCursor = cursor; cursor.beginEditBlock(); if ( placement == End ) cursor.movePosition( QTextCursor::End ); else if ( placement == Start ) cursor.movePosition( QTextCursor::Start ); textEdit->setTextCursor( cursor ); // Insert the signature and newlines depending on where it was inserted. bool hackForCursorsAtEnd = false; int oldCursorPos = -1; if ( placement == End ) { if ( oldCursor.position() == textEdit->toPlainText().length() ) { hackForCursorsAtEnd = true; oldCursorPos = oldCursor.position(); } if ( isHtml ) { textEdit->insertHtml( QLatin1String( "
" ) + signature ); } else { textEdit->insertPlainText( QLatin1Char( '\n' ) + signature ); } } else if ( placement == Start || placement == AtCursor ) { if ( isHtml ) { textEdit->insertHtml( QLatin1String( "
" ) + signature + QLatin1String( "
" ) ); } else { textEdit->insertPlainText( QLatin1Char( '\n' ) + signature + QLatin1Char( '\n' ) ); } } cursor.endEditBlock(); // There is one special case when re-setting the old cursor: The cursor // was at the end. In this case, QTextEdit has no way to know // if the signature was added before or after the cursor, and just decides // that it was added before (and the cursor moves to the end, but it should // not when appending a signature). See bug 167961 if ( hackForCursorsAtEnd ) oldCursor.setPosition( oldCursorPos ); textEdit->setTextCursor( oldCursor ); textEdit->ensureCursorVisible(); textEdit->document()->setModified( isModified ); if ( isHtml ) { textEdit->enableRichTextMode(); } } } // --------------------- Operators -------------------// QDataStream &KPIMIdentities::operator<< ( QDataStream &stream, const KPIMIdentities::Signature &sig ) { return stream << static_cast( sig.mType ) << sig.mUrl << sig.mText << d( &sig )->saveLocation << d( &sig )->embeddedImages; } QDataStream &KPIMIdentities::operator>> ( QDataStream &stream, KPIMIdentities::Signature &sig ) { quint8 s; stream >> s >> sig.mUrl >> sig.mText >> d( &sig )->saveLocation >> d( &sig )->embeddedImages; sig.mType = static_cast( s ); return stream; } bool Signature::operator== ( const Signature &other ) const { if ( mType != other.mType ) { return false; } if ( mType == Inlined && mInlinedHtml ) { if ( d( this )->saveLocation != d( &other )->saveLocation ) return false; if ( d( this )->embeddedImages != d( &other )->embeddedImages ) return false; } switch ( mType ) { case Inlined: return mText == other.mText; case FromFile: case FromCommand: return mUrl == other.mUrl; default: case Disabled: return true; } } QString Signature::plainText() const { QString sigText = rawText(); if ( isInlinedHtml() && type() == Inlined ) { // Use a QTextDocument as a helper, it does all the work for us and // strips all HTML tags. QTextDocument helper; QTextCursor helperCursor( &helper ); helperCursor.insertHtml( sigText ); sigText = helper.toPlainText(); } return sigText; } void Signature::addImage ( const QImage& imageData, const QString& imageName ) { Q_ASSERT( !( d( this )->saveLocation.isEmpty() ) ); SignaturePrivate::EmbeddedImagePtr image( new SignaturePrivate::EmbeddedImage() ); image->image = imageData; image->name = imageName; d( this )->embeddedImages.append( image ); } void Signature::setImageLocation ( const QString& path ) { d( this )->saveLocation = path; } // --------------- Getters -----------------------// QString Signature::text() const { return mText; } QString Signature::url() const { return mUrl; } Signature::Type Signature::type() const { return mType; } // --------------- Setters -----------------------// void Signature::setText( const QString &text ) { mText = text; mType = Inlined; } void Signature::setType( Type type ) { mType = type; } diff --git a/kpimidentities/signature.h b/kpimidentities/signature.h index 0de5aa3dd..ec5165cb2 100644 --- a/kpimidentities/signature.h +++ b/kpimidentities/signature.h @@ -1,265 +1,276 @@ /* Copyright (c) 2002-2004 Marc Mutz Copyright (c) 2007 Tom Albers Copyright (c) 2009 Thomas McGuire Author: Stefan Taferner This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KPIMIDENTITIES_SIGNATURE_H #define KPIMIDENTITIES_SIGNATURE_H #include "kpimidentities_export.h" #include #include #include #include #include #include namespace KPIMIdentities { class Signature; class Identity; } class KConfigGroup; class KRichTextEdit; namespace KPIMIdentities { KPIMIDENTITIES_EXPORT QDataStream &operator<< ( QDataStream &stream, const KPIMIdentities::Signature &sig ); KPIMIDENTITIES_EXPORT QDataStream &operator>> ( QDataStream &stream, KPIMIdentities::Signature &sig ); /** * @short Abstraction of a signature (aka "footer"). * * The signature can either be plain text, HTML text, text returned from a command or text stored * in a file. * * In case of HTML text, the signature can contain images. * Since you set the HTML source with setText(), there also needs to be a way to add the images * to the signature, as the HTML source contains only the img tags that reference those images. * To add the image to the signature, call addImage(). The name given there must match the name * of the img tag in the HTML source. * * The images need to be stored somewhere. The Signature class handles that by storing all images * in a directory. You must set that directory with setImageLocation(), before calling addImage(). * The images added with addImage() are then saved to that directory when calling writeConfig(). * When loading a signature, readConfig() automatically loads the images as well. * To actually add the images to a text edit, call insertIntoTextEdit(). */ class KPIMIDENTITIES_EXPORT Signature { friend class Identity; friend KPIMIDENTITIES_EXPORT QDataStream &operator<< ( QDataStream &stream, const Signature &sig ); friend KPIMIDENTITIES_EXPORT QDataStream &operator>> ( QDataStream &stream, Signature &sig ); public: /** Type of signature (ie. way to obtain the signature text) */ enum Type { Disabled = 0, Inlined = 1, FromFile = 2, FromCommand = 3 }; /** * Describes the placement of the signature text when it is to be inserted into a * text edit */ enum Placement { Start, ///< The signature is placed at the start of the textedit End, ///< The signature is placed at the end of the textedit AtCursor ///< The signature is placed at the current cursor position }; /** Used for comparison */ bool operator== ( const Signature &other ) const; /** Constructor for disabled signature */ Signature(); /** Constructor for inline text */ Signature( const QString &text ); /** Constructor for text from a file or from output of a command */ Signature( const QString &url, bool isExecutable ); /** Copy constructor */ Signature( const Signature &that ); /** Assignment operator */ Signature& operator= ( const Signature &that ); /** Destructor */ ~Signature(); /** @return the raw signature text as entered resp. read from file. */ QString rawText( bool *ok=0 ) const; /** @return the signature text with a "-- \n" separator added, if necessary. A newline will not be appended or prepended. */ QString withSeparator( bool *ok=0 ) const; /** Set the signature text and mark this signature as being of "inline text" type. */ void setText( const QString &text ); QString text() const; /** * Returns the text of the signature. If the signature is HTML, the HTML * tags will be stripped. * @since 4.4 */ QString plainText() const; /** Set the signature URL and mark this signature as being of "from file" resp. "from output of command" type. */ void setUrl( const QString &url, bool isExecutable=false ); QString url() const; /// @return the type of signature (ie. way to obtain the signature text) Type type() const; void setType( Type type ); /** * Sets the inlined signature to text or html * @param isHtml sets the inlined signature to html * @since 4.1 */ void setInlinedHtml( bool isHtml ); /** * @return boolean whether the inlined signature is html * @since 4.1 */ bool isInlinedHtml() const; /** * Sets the location where the copies of the signature images will be stored. * The images will be stored there when calling writeConfig(). The image location * is stored in the config, so the next readConfig() call knows where to look for * images. * It is recommended to use KStandardDirs::locateLocal( "data", "emailidentities/%1" ) * for the location, where %1 is the unique identifier of the identity. * * @warning readConfig will delete all other PNG files in this directory, as they could * be stale inline image files * * Like with addImage(), the SignatureConfigurator will handle this for you. * * @since 4.4 */ void setImageLocation( const QString &path ); /** * Adds the given image to the signature. * This is needed if you use setText() to set some HTML source that references images. Those * referenced images needed to be added by calling this function. The @imageName has to match * the src attribute of the img tag. * * If you use SignatureConfigurator, you don't need to call this function, as the configurator * will handle this for you. * setImageLocation() needs to be called once before. * @since 4.4 */ void addImage( const QImage &image, const QString &imageName ); /** * Inserts this signature into the given text edit. * The cursor position is preserved. * A leading or trailing newline is also added automatically, depending on * the placement. * For undo/redo, this is treated as one operation. * * Rich text mode of the text edit will be enabled if the signature is in * inlined HTML format. * * If this signature uses images, they will be added automatically. * * @param textEdit the signature will be inserted into this text edit. * @param placement defines where in the text edit the signature should be * inserted. * @param addSeparator if true, the separator '-- \n' will be added in front * of the signature * * @since 4.3 * TODO: KDE5: BIC: remove, as we have a const version below * TODO: KDE5: BIC: Change from KRichTextEdit to KPIMTextEdit::TextEdit, to avoid * the dynamic_cast used here */ void insertIntoTextEdit( KRichTextEdit *textEdit, Placement placement = End, bool addSeparator = true ); /** * Same as the other insertIntoTextEdit(), only that this is the const version * @since 4.4 */ void insertIntoTextEdit( KRichTextEdit *textEdit, Placement placement = End, bool addSeparator = true ) const; /** * Inserts this given signature into the given text edit. * The cursor position is preserved. * A leading or trailing newline is also added automatically, depending on * the placement. * For undo/redo, this is treated as one operation. * A separator is not added. * * Use the insertIntoTextEdit() function if possible, as it has support * for separators and does HTML detection automatically. * * Rich text mode of the text edit will be enabled if @p isHtml is true. * * @param signature the signature, either as plain text or as HTML * @param textEdit the text edit to insert the signature into * @param placement defines where in the textedit the signature should be * inserted. * @param isHtml defines whether the signature should be inserted as text or html * * @since 4.3 */ static void insertPlainSignatureIntoTextEdit( const QString &signature, KRichTextEdit *textEdit, Placement placement = End, bool isHtml = false ); protected: void writeConfig( KConfigGroup &config ) const; void readConfig( const KConfigGroup &config ); /** * Helper used for the copy constructor and the assignment operator */ void assignFrom( const Signature &that ); + /** + * Clean up unused images from our internal list and delete all images + * from the file system + */ + void cleanupImages() const; + + /** + * Saves all images from our internal list to the file system. + */ + void saveImages() const; + private: QString textFromFile( bool *ok ) const; QString textFromCommand( bool *ok ) const; // TODO: KDE5: BIC: Add a d-pointer!!! // There is already a pseude private class in the .cpp, using a hash. QString mUrl; QString mText; Type mType; bool mInlinedHtml; }; } #endif /*kpim_signature_h*/ diff --git a/kpimidentities/tests/signaturetest.cpp b/kpimidentities/tests/signaturetest.cpp index 91d30ed9c..ffb873631 100644 --- a/kpimidentities/tests/signaturetest.cpp +++ b/kpimidentities/tests/signaturetest.cpp @@ -1,150 +1,229 @@ /* Copyright (c) 20089 Thomas McGuire This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qtest_kde.h" #include "signaturetest.h" #include "../signature.h" #include "kpimtextedit/textedit.h" +#include +#include +#include + using namespace KPIMIdentities; using namespace KPIMTextEdit; QTEST_KDEMAIN( SignatureTester, GUI ) void SignatureTester::testSignatures() { Signature sig1; sig1.setText( "Hello World" ); QCOMPARE( sig1.text(), QString( "Hello World" ) ); QCOMPARE( sig1.type(), Signature::Inlined ); QCOMPARE( sig1.rawText(), QString( "Hello World" ) ); QVERIFY( !sig1.isInlinedHtml() ); QCOMPARE( sig1.withSeparator(), QString( "-- \nHello World" ) ); Signature sig2; sig2.setText( "Hello World" ); sig2.setInlinedHtml( true ); QVERIFY( sig2.isInlinedHtml() ); QCOMPARE( sig2.type(), Signature::Inlined ); QCOMPARE( sig2.rawText(), QString( "Hello World" ) ); QCOMPARE( sig2.withSeparator(), QString( "--
Hello World" ) ); // Read this very file in, we use it for the tests QFile thisFile( __FILE__ ); thisFile.open( QIODevice::ReadOnly ); QString fileContent = QString::fromUtf8( thisFile.readAll() ); Signature sig3; sig3.setUrl( QString( "cat " ) + QString( __FILE__ ), true ); QCOMPARE( sig3.rawText(), fileContent ); QVERIFY( !sig3.isInlinedHtml() ); QVERIFY( sig3.text().isEmpty() ); QCOMPARE( sig3.type(), Signature::FromCommand ); QCOMPARE( sig3.withSeparator(), QString( "-- \n" ) + fileContent ); Signature sig4; sig4.setUrl( __FILE__, false ); QCOMPARE( sig4.rawText(), fileContent ); QVERIFY( !sig4.isInlinedHtml() ); QVERIFY( sig4.text().isEmpty() ); QCOMPARE( sig4.type(), Signature::FromFile ); QCOMPARE( sig4.withSeparator(), QString( "-- \n" ) + fileContent ); } static void setCursorPos( QTextEdit &edit, int pos ) { QTextCursor cursor( edit.document() ); cursor.setPosition( pos ); edit.setTextCursor( cursor ); } void SignatureTester::testTextEditInsertion() { TextEdit edit; Signature sig; sig.setText( "Hello World" ); // Test inserting signature at start, with seperators edit.setPlainText( "Bla Bla" ); sig.insertIntoTextEdit( &edit, Signature::Start, true ); QVERIFY( edit.textMode() == KRichTextEdit::Plain ); QCOMPARE( edit.toPlainText(), QString( "\n-- \nHello World\nBla Bla" ) ); // Test inserting signature at end. make sure cursor position is preserved edit.clear(); edit.setPlainText( "Bla Bla" ); setCursorPos( edit, 4 ); sig.insertIntoTextEdit( &edit, Signature::End, true ); QCOMPARE( edit.toPlainText(), QString( "Bla Bla\n-- \nHello World" ) ); QCOMPARE( edit.textCursor().position(), 4 ); // test inserting a signature at cursor position. make sure the cursor // moves the position correctly. make sure modified state is preserved edit.clear(); edit.setPlainText( "Bla Bla" ); setCursorPos( edit, 4 ); edit.document()->setModified( false ); sig.insertIntoTextEdit( &edit, Signature::AtCursor, true ); QCOMPARE( edit.toPlainText(), QString( "Bla \n-- \nHello World\nBla" ) ); QCOMPARE( edit.textCursor().position(), 21 ); QVERIFY( !edit.document()->isModified() ); // make sure undo undoes everything in one go edit.undo(); QCOMPARE( edit.toPlainText(), QString( "Bla Bla" ) ); // test inserting signature without seperator. // make sure cursor position and modified state is preserved. edit.clear(); edit.setPlainText( "Bla Bla" ); setCursorPos( edit, 4 ); edit.document()->setModified( true ); sig.insertIntoTextEdit( &edit, Signature::End, false ); QCOMPARE( edit.toPlainText(), QString( "Bla Bla\nHello World" ) ); QCOMPARE( edit.textCursor().position(), 4 ); QVERIFY( edit.document()->isModified() ); sig.setText( "Hello
World" ); sig.setInlinedHtml( true ); // test that html signatures turn html on and have correct line endings (
vs \n) edit.clear(); edit.setPlainText( "Bla Bla" ); sig.insertIntoTextEdit( &edit, Signature::End, true ); QVERIFY( edit.textMode() == KRichTextEdit::Rich ); QCOMPARE( edit.toPlainText(), QString( "Bla Bla\n-- \nHello\nWorld" ) ); } void SignatureTester::testBug167961() { TextEdit edit; Signature sig; sig.setText( "BLA" ); // Test that the cursor is still at the start when appending a sig into // an empty text edit sig.insertIntoTextEdit( &edit, Signature::End, true ); QCOMPARE( edit.textCursor().position(), 0 ); // OTOH, when prepending a sig, the cursor should be at the end edit.clear(); sig.insertIntoTextEdit( &edit, Signature::Start, true ); QCOMPARE( edit.textCursor().position(), 9 ); // "\n-- \nBLA\n" } + +// Make writeConfig() public, we need it +class MySignature : public Signature +{ + public: + using Signature::writeConfig; + using Signature::readConfig; +}; + +void SignatureTester::testImages() +{ + TextEdit edit; + QString image1Path = KIconLoader::global()->iconPath( QLatin1String( "folder-new" ), KIconLoader::Small, false ); + QString image2Path = KIconLoader::global()->iconPath( QLatin1String( "arrow-up" ), KIconLoader::Small, false ); + QImage image1, image2; + QVERIFY( image1.load( image1Path ) ); + QVERIFY( image2.load( image1Path ) ); + QString path = KStandardDirs::locateLocal( "data", "emailidentities/unittest/" ); + QString configPath = KStandardDirs::locateLocal( "config", "signaturetest" ); + KConfig config( configPath ); + KConfigGroup group1 = config.group( "Signature1" ); + + MySignature sig; + sig.setImageLocation( path ); + sig.setInlinedHtml( true ); + sig.setText( "BlaBla" ); + sig.addImage( image1, "folder-new.png" ); + sig.writeConfig( group1 ); + + // OK, signature saved, the image should be saved as well + QDir dir( path ); + QStringList entryList = dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ); + QCOMPARE( entryList.count(), 1 ); + QCOMPARE( entryList.first(), QString( "folder-new.png" ) ); + + // Now, set the text of the signature to something without images, then save it. + // The signature class should have removed the images. + sig.setText( "ascii ribbon campaign - against html mail" ); + sig.writeConfig( group1 ); + entryList = dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ); + QCOMPARE( entryList.count(), 0 ); + + // Enable images again, this time with two of the buggers + sig.setText( "BlaBlaBla" ); + sig.addImage( image1, "folder-new.png" ); + sig.addImage( image2, "arrow-up.png" ); + sig.writeConfig( group1 ); + entryList = dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ); + QCOMPARE( entryList.count(), 2 ); + QCOMPARE( entryList.first(), QString( "arrow-up.png" ) ); + QCOMPARE( entryList.last(), QString( "folder-new.png" ) ); + + // Now, create a second signature instance from the same config, and make sure it can still + // read the images, and it does not mess up + MySignature sig2; + sig2.readConfig( group1 ); + sig2.insertIntoTextEdit( &edit ); + QCOMPARE( edit.embeddedImages().count(), 2 ); + QCOMPARE( sig2.text(), QString( "BlaBlaBla") ); + sig2.writeConfig( group1 ); + entryList = dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ); + QCOMPARE( entryList.count(), 2 ); + QCOMPARE( entryList.first(), QString( "arrow-up.png" ) ); + QCOMPARE( entryList.last(), QString( "folder-new.png" ) ); + + // Remove one image from the signature, and make sure only 1 file is left one file system. + sig2.setText( "" ); + sig2.writeConfig( group1 ); + edit.clear(); + sig2.insertIntoTextEdit( &edit ); + QCOMPARE( edit.embeddedImages().size(), 1 ); + entryList = dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ); + QCOMPARE( entryList.count(), 1 ); +} + diff --git a/kpimidentities/tests/signaturetest.h b/kpimidentities/tests/signaturetest.h index 93c3f8e24..daaff9b69 100644 --- a/kpimidentities/tests/signaturetest.h +++ b/kpimidentities/tests/signaturetest.h @@ -1,35 +1,36 @@ /* Copyright (c) 2009 Thomas McGuire This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SIGNATURETEST_H #define SIGNATURETEST_H #include class SignatureTester : public QObject { Q_OBJECT private slots: void testSignatures(); void testTextEditInsertion(); void testBug167961(); + void testImages(); }; #endif